from argparse import ArgumentParser, Namespace
from typing import List
import git.exc
from west.commands import WestCommand
import west
from west import log
from textwrap import dedent
import os
from enum import Enum
import west.commands
import west.manifest
import west.util
import yaml
from pathlib import Path
import shutil
from jinja2 import Environment, FileSystemLoader
import git

copyright = "Copyright (c) Sonosemi Medical Ltd.\n"
project_path = Path(west.util.west_topdir())
manifest_path = west.manifest.manifest_path()
module_path=f"{project_path}/modules"
current_path=os.path.dirname(__file__)

def GenerateFileFromTemplate(template_file_name: str, path: str, generated_file_name: str, **kwargs):
    try:
        env = Environment(loader=FileSystemLoader(current_path))
        template = env.get_template(template_file_name)
        rendered_content = template.render(**kwargs)
        with open(os.path.join(path,generated_file_name), 'w') as f:
            f.write(rendered_content)
        print(f"generated {generated_file_name}")
    except Exception as e:
        print(f"error happens when generating {generated_file_name}: {e}")

class Driver:
    def __init__(self, name:str, deps):
        self.deps = deps
        self.name = name
        self.upper_name = name.upper()
        self.path = os.path.join(module_path, name)
        os.mkdir(self.path)
        self.doc_path = os.path.join(self.path, "doc")
        os.mkdir(self.doc_path)
        self.src_path=os.path.join(self.path, "src")
        os.mkdir(self.src_path)
        self.module_file_path=os.path.join(self.path, "zephyr")
        os.mkdir(self.module_file_path)
        self.bindings_path = f"{self.path}/dts/bindings"
        os.makedirs(self.bindings_path)
        self.include_path = os.path.join(self.path, "include")
        self.syscall_header_path = f"{self.include_path}/zephyr/drivers"
        os.makedirs(self.syscall_header_path)
        open(os.path.join(self.path, "README.md"), 'w')
        self.GenerateCmake()
        self.GenerateCHeader()
        self.GenerateModuleFile()
        self.GenerateKconfig()
        self.GenerateTests()
        self.InitGit()
    def GenerateCHeader(self):
        GenerateFileFromTemplate("zephyr_driver.h.in", 
                                 self.syscall_header_path, 
                                 f"{self.name}.h", 
                                 driver=self.name, 
                                 DRIVER=self.name.upper())

    def GenerateModuleFile(self):
        module_data = {
            "name":self.name,
            "build": {
                "depends": self.deps,
                "cmake": ".",
                "kconfig": "Kconfig",
                "settings": {
                    "dts_root": "."
                }
            }
        }
        with open(os.path.join(self.module_file_path, "module.yaml"), "w") as f:
            data = yaml.safe_dump(module_data, f)
    def GenerateCmake(self):
        GenerateFileFromTemplate("driver_CMakeLists.txt.in", 
                                 self.path, 
                                 "CMakeLists.txt", 
                                 module=self.name, 
                                 MODULE=self.name.upper())
    def GenerateKconfig(self):
        template_file = os.path.join(current_path, "Kconfig.in")
        GenerateFileFromTemplate("Kconfig.in", 
                                 self.path, 
                                 "Kconfig", 
                                 driver=self.name, 
                                 DRIVER=self.name.upper())

    def GenerateTests(self):
        tests_dir = os.path.join(self.path, "tests")
        os.mkdir(tests_dir)
        board_dir = os.path.join(tests_dir, "boards")
        os.mkdir(board_dir)
        with open(os.path.join(board_dir, f"test_{self.name}.overlay"), "w") as f:
            f.write("/ {\r\n\r\n};")
        shutil.copy(os.path.join(current_path, "test_main.cpp.in"), os.path.join(tests_dir, "test_main.cpp"))
        GenerateFileFromTemplate("test_prj.conf.in", tests_dir, "prj.conf", MODULE=self.name.upper())
        testcase_data = {
            "tests":{
                f"{self.name}_driver.utest":{
                    "platform_allow" : [
                        "native_sim/native/64"
                    ],
                    "harness": "gtest",
                    "extra_args": f"DTC_OVERLAY_FILE=boards/test_{self.name}.overlay"
                }
            }
        }
        with open(os.path.join(tests_dir, "testcase.yaml"), "w") as f:
            yaml.safe_dump(testcase_data, f)
    def InitGit(self):
        try:
            repo = git.Repo.init(self.path)
        except git.exc.GitError:
            print(f"Failed to initialized a git repository at {self.path}")
class CommentType(Enum):
    script = "#" 
    c_source = "//"

def FileAddCopyright(file: str, type: CommentType): 
    file.write(type.value + copyright)

def RemoveModuleFromManifest(module:str):
    try:
        with open(manifest_path, 'r') as f:
            data = yaml.safe_load(f)

        projects = data.get('manifest', {}).get('projects', [])
        new_projects = []
        for project in projects:
            if project.get('name')!= module:
                new_projects.append(project)

        if 'manifest' in data:
            data['manifest']['projects'] = new_projects
        else:
            data['manifest'] = {'projects': new_projects}

        with open(manifest_path, 'w') as f:
            yaml.safe_dump(data, f)
            print(f"Successfully removed module {module} from the manifest.")
    except FileNotFoundError:
        print("west.yml file not found.")
    except yaml.YAMLError as e:
        print(f"Error while handling YAML: {e}")

def AddModuleFromManifest(module: str, url="undefined", rversion="main"):
    try:
        with open(manifest_path, 'r') as f:
            manifest = yaml.safe_load(f)

        projects = manifest.get('manifest', {}).get('projects', [])
        new_project = {
            "name": module,
            "path": f"modules/{module}",
            "revision": rversion,
            "url": url
        }
        projects.append(new_project)
        if 'manifest' in manifest:
            manifest['manifest']['projects'] = projects
        else:
            manifest['manifest'] = {'projects': projects}

        with open(manifest_path, 'w') as f:
            yaml.safe_dump(manifest, f)

        print(f"Successfully added module {module} to the west.yml manifest.")
    except FileNotFoundError:
        print("west.yml file not found.")
    except yaml.YAMLError as e:
        print(f"Error while handling YAML: {e}")

def RemoveModule(name: str):
    item_path = os.path.join(module_path, name)
    try:
        shutil.rmtree(item_path)
    except FileNotFoundError:
        pass
    RemoveModuleFromManifest(name)

def AddDriver(name: str, url: str, deps):
    driver_path = os.path.join(module_path, name)

    try:
        driver = Driver(name, deps)
        AddModuleFromManifest(name, url)
    except FileExistsError:
        print(f"ERROR: The module path {driver_path} already exists.")
def AddLibrary(name:str, path:str):
    #TODO
    pass
def AddModule(name:str, path:str, type:str, deps):
    if type == "driver":
        AddDriver(name, path)
    elif type == "library":
        AddLibrary(name, path)
    else:
        #TODO
        pass
    
class ModuleCommand(WestCommand):
    def __init__(self):
        super().__init__(
            name="module",
            help="operate modules in the project",
            description='''
            "operate modules in the project"
            ''',
            accepts_unknown_args=False
        )

    def do_add_parser(self, parser_adder) -> ArgumentParser:
        parser = parser_adder.add_parser(self.name, 
                                         help = self.help,
                                        description = self.description)
        parser.add_argument("-a", "--add", help="add a module in the project")
        parser.add_argument("-t", "--type", help="module type", choices=["driver", "library"])
        parser.add_argument("-d", "--delete", help="remove a module from this project")
        parser.add_argument("-u", "--url", help="specify a url address for the module to add", default="undefined")
        parser.add_argument("--deps", help="dependencies of the module to add", default=[], action="append")
        return parser
    def do_run(self, args: Namespace, unknown: List[str]):
        module_path = f"{project_path}/modules"
        if not args.add is None:
            if args.type == "driver":
                AddDriver(name=args.add, url=args.url, deps=args.deps)
            elif args.type == "library":
                AddModule(name=args.add, url=args.url, deps=args.deps)
            else:
                self.parser.error(f"must specify a type when adding a module")
                self.parser.print_usage()                
                # raise west.commands.CommandError
        elif not args.delete is None:
            print(f"Deleting module {args.delete}")
            RemoveModule(name=args.delete)

     